package dnl.jexem.cmd;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;

/**
 *
 * @author Daniel Orr
 */
public class NonBlockingIoHandler implements ProcessIoHandler {
    enum IoType {
        OUT, ERR
    };
    
    private final List<ProcessOutputListener> outputListeners;
    private final ThreadGroup threadGroup;
    private IoReadingThread standardOutReadingThread;
    private IoReadingThread standardErrReadingThread;

    public NonBlockingIoHandler(Command command, List<ProcessOutputListener> outputListeners) {
        this.outputListeners = outputListeners;
        threadGroup = new ThreadGroup(command.getCommand() + ":" + command.getId());
    }

    @Override
    public void handleProcessIO(InputStream standardOut, InputStream standardErr) throws IOException {
        Reader processStandardOut = new BufferedReader(new InputStreamReader(standardOut));
        Reader processStandardErr = new BufferedReader(new InputStreamReader(standardErr));
        standardOutReadingThread = new IoReadingThread(threadGroup, IoType.OUT, processStandardOut);
        standardErrReadingThread = new IoReadingThread(threadGroup, IoType.ERR, processStandardErr);
        standardOutReadingThread.start();
        standardErrReadingThread.start();
    }

    @Override
    public String getStandardOut() {
        return standardOutReadingThread.out.toString();
    }

    @Override
    public String getStandardErr() {
        return standardErrReadingThread.out.toString();
    }

    private void notifyListenersNewStdOut(String newText) {
        for (ProcessOutputListener processOutputListener : outputListeners) {
            processOutputListener.standardOutputChanged(newText);
        }
    }
    private void notifyListenersNewStdErr(String newText) {
        for (ProcessOutputListener processOutputListener : outputListeners) {
            processOutputListener.standardErrorChanged(newText);
        }
    }

    @Override
    public void kill() {
        if (threadGroup.activeCount() > 0) {
            threadGroup.stop();
        }
    }

    private class IoReadingThread extends Thread {
        private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
        final StringWriter out = new StringWriter();
        final Reader in;
        IoType ioType;
        String err;

        public IoReadingThread(ThreadGroup threadGroup, IoType ioType, Reader in) {
            super(threadGroup, IoType.OUT.equals(ioType)? "StandardOut" : "StandardErr");
            this.in = in;
            this.ioType = ioType;
        }

        @Override
        public void run() {
            try {
                copyLarge(in, out);
            } catch (IOException ex) {
                err = "IO error: " + ex.getMessage();
            }
        }

        public long copyLarge(Reader input, Writer output) throws IOException {
            return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);
        }

        public long copyLarge(Reader input, Writer output, char[] buffer) throws IOException {
            long count = 0;
            int n = 0;
            while (-1 != (n = input.read(buffer))) {
                output.write(buffer, 0, n);
                if (containsNewLine(buffer)) {
                    output.flush();
                    switch(ioType){
                        case OUT: 
                            notifyListenersNewStdOut(new String(buffer));
                            break;
                        case ERR: 
                            notifyListenersNewStdErr(new String(buffer));
                            break;
                    }
                }
                count += n;
            }
            return count;
        }

        public boolean containsNewLine(char[] carr) {
            for (int i = 0; i < carr.length; i++) {
                if (carr[i] == '\n') {
                    return true;
                }
            }
            return false;
        }
    }

}
